/**
 * UTF8 to UCS4 conversion facilities.
 *
 * Contains slightly modified code of LibParserUtils
 * Original and this code is licensed under the MIT License,
 * http://www.opensource.org/licenses/mit-license.php
 * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org>
 *
 * A. Shabanov, 2009
 */

#include "utf8.h"

#include <wctype.h>

#include "utf8impl.h"

void ucs4_tolower(uint32_t * ucs4, uint32_t * ucs4_end)
{
    while (ucs4 < ucs4_end)
    {
        *ucs4 = towlower(*ucs4);
        ++ucs4;
    }
}

uint32_t * utf8_to_ucs4_nocheck(uint8_t * utf8, uint8_t * utf8end, uint32_t * ucs4)
{
    int error = 0;

    for (;utf8 < utf8end;)
    {
        uint32_t c, min;
        uint8_t n;
        uint8_t i;

        c = *utf8++;

        if (c == 0)
        {
            // last entry
            break;
        }

        if (c < 0x80)
        {
            n = 1;
            min = 0;
        }
        else if ((c & 0xE0) == 0xC0)
        {
            c &= 0x1F;
            n = 2;
            min = 0x80;
        }
        else if ((c & 0xF0) == 0xE0)
        {
            c &= 0x0F;
            n = 3;
            min = 0x800;
        }
        else if ((c & 0xF8) == 0xF0)
        {
            c &= 0x07;
            n = 4;
            min = 0x10000;
        }
        else if ((c & 0xFC) == 0xF8)
        {
            c &= 0x03;
            n = 5;
            min = 0x200000;
        }
        else if ((c & 0xFE) == 0xFC)
        {
            c &= 0x01;
            n = 6;
            min = 0x4000000;
        }
        else
        {
            // unknown sequence
            error = -1;
            break;
        }

        if (!error)
        {
            for (i = 1; i < n; i++)
            {
                if (utf8 >= utf8end)
                {
                    // broken sequence
                    error = -1;
                    break;
                }

                uint32_t t = *utf8++;

                if ((t & 0xC0) != 0x80)
                {
                    // invalid sequence
                    error = -1;
                    break;
                }

                c <<= 6;
                c |= t & 0x3F;
            }
        }

        if (!error)
        {
            /* Detect overlong sequences, surrogates and fffe/ffff */
            if (c < min || (c >= 0xD800 && c <= 0xDFFF) ||
                c == 0xFFFE || c == 0xFFFF)
            {
                error = -1;
                break;
            }

            *ucs4++ = c;
        }


        if (error)
        {
            break;
        }
    }

    // last entry or error
    return (error == 0 ? ucs4 : 0);
}


int utf8_to_ucs4(const uint8_t *s, size_t len, uint32_t *ucs4, size_t *clen)
{
    int error;
    UTF8_TO_UCS4(s, len, ucs4, clen, error);
    return error;
}


int utf8_from_ucs4(uint32_t ucs4, uint8_t **s, size_t *len)
{
    int error;
    UTF8_FROM_UCS4(ucs4, s, len, error);
    return error;
}


/** Number of continuation bytes for a given start byte */
static const uint8_t numContinuations[256] =
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
};



int utf8_length(const uint8_t *s, size_t max, size_t *len)
{
    int error;
    UTF8_LENGTH(s, max, len, error);
    return error;
}


int utf8_char_byte_length(const uint8_t *s, size_t *len)
{
    int error;
    UTF8_CHAR_BYTE_LENGTH(s, len, error, numContinuations);
    return error;
}


int utf8_prev(const uint8_t *s, uint32_t off, uint32_t *prevoff)
{
    int error;
    UTF8_PREV(s, off, prevoff, error);
    return error;
}


int utf8_next(const uint8_t *s, uint32_t len, uint32_t off, uint32_t *nextoff)
{
    int error;
    UTF8_NEXT(s, len, off, nextoff, error);
    return error;
}


int utf8_next_paranoid(const uint8_t *s, uint32_t len, uint32_t off, uint32_t *nextoff)
{
    int error;
    UTF8_NEXT_PARANOID(s, len, off, nextoff, error, numContinuations);
    return error;
}

